﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using LinFu.Reflection;
using Mono.Cecil;

namespace CoreEx.Common.Ioc
{
    /// <summary>
    /// An <see cref="IAssemblyLoader"/> implementation that is capable of weaving the assembly 
    /// before it gets loaded into the current <see cref="AppDomain"/>.
    /// </summary>
    [Implements(typeof(IAssemblyLoader),ServiceName = "WeavedAssemblyLoader")]
    public class WeaveTargetAssemblyLoader : IAssemblyLoader, IInitialize
    {
        private IServiceContainer _serviceContainer;

        private IDictionary<string, IList<Type>> _assemblyWeavers;        
        
        ///<summary>        
        /// Loads the target assembly into memory.        
        ///</summary>                
        /// <seealso cref="IImplementor"/>
        ///<param name="assemblyFile">The full path and filename of the assembly to load.</param>
        ///<returns>
        ///A loaded <see cref="Assembly" /> instance.
        ///</returns>        
        public Assembly Load(string assemblyFile)
        {                                    
            if (assemblyFile == null)
                throw new ArgumentNullException("assemblyFile");




            string assemblyBackupFile = Path.GetTempFileName();
            
            string pdbFile = string.Format("{0}.pdb", Path.GetFileNameWithoutExtension(assemblyFile));
            string pdbFileTemp = Path.GetTempFileName();
            
            //Check to see if the symbols file is available
            bool hasSymbols = File.Exists(pdbFile) && !IsFileLocked(pdbFile);
            
            try
            {

                byte[] assemblyArray;

                //Create a copy of the original assembly.
                //Mono.Cecil updates the header information in the assembly if dealing with symbols and 
                //we want our assembly to remain untouched
                File.Copy(assemblyFile,assemblyBackupFile,true);
                                
                var assemblyDefinition = AssemblyFactory.GetAssembly(assemblyFile);
                                              
                if (hasSymbols)
                {
                    //Create a copy of the pdb file.
                    //Mono.Cecil can only update the actual file and create a backup that can be restored later.
                    //Again we want our original files to remain untouched.
                    File.Copy(pdbFile, pdbFileTemp,true);                                        
                    assemblyDefinition.MainModule.LoadSymbols();                    
                }


                //Verify that the current assembly is registered as a weave target.
                var assemblyFileName = Path.GetFileName(assemblyFile);
                if (_assemblyWeavers.ContainsKey(assemblyFileName))
                    foreach (var weaverType in _assemblyWeavers[assemblyFileName])
                    {
                        //The actual weaving is handed of to one or more IAssemblyWeaver instances.
                        var weaver = Activator.CreateInstance(weaverType) as IAssemblyWeaver;

                        if (weaver != null)
                        {
                            ((IInitialize) weaver).Initialize(_serviceContainer);
                            weaver.Weave(assemblyDefinition);
                        }
                    }
            
                //Updates the debug symbols
                if (hasSymbols)                    
                    assemblyDefinition.MainModule.SaveSymbols();                    
                
                //Save the assembly out to a byte array
                AssemblyFactory.SaveAssembly(assemblyDefinition,out assemblyArray);

                //Load up the assembly into the current application domain
                if (hasSymbols)                    
                    return Assembly.Load(assemblyArray, File.ReadAllBytes(pdbFile));
                else
                    return Assembly.Load(assemblyArray);
            }
             
            //catch(Exception ex)
            //{
            //    Console.WriteLine("");
            //    return null;
            //}

            finally
            {
                if (hasSymbols)
                {
                    //Clean up
                    if (File.Exists(pdbFileTemp))
                    {
                        File.Copy(pdbFileTemp, pdbFile, true);
                        File.Delete(pdbFileTemp);
                    }
                }

                if (File.Exists(assemblyBackupFile))
                {
                    File.Copy(assemblyBackupFile,assemblyFile,true);
                    File.Delete(assemblyBackupFile);
                }
            }
        }

        
        private bool IsFileLocked(string fileName)
        {
            if (!File.Exists(fileName)) 
                return false;
            try
            {
                FileStream fileStream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite); 
                fileStream.Close();
            }
            catch
            {
                return true;
            }
            return false;
        }


        /// <summary>
        /// Initializes the <see cref="WeaveTargetAssemblyLoader"/> instance.
        /// </summary>
        /// <param name="source">The <see cref="IServiceContainer"/> instance 
        /// used to resolve this  <see cref="WeaveTargetAssemblyLoader"/> instance.</param>
        public void Initialize(IServiceContainer source)
        {
            _assemblyWeavers = source.GetService<IDictionary<string, IList<Type>>>("AssemblyWeavers");
            _serviceContainer = source;
        }
    }
}
